package com.cly.filter;

import com.cly.base.contants.CacheKeysConstants;
import com.cly.base.contants.UserConstants;
import com.cly.entity.Router;
import com.cly.result.JsonRequest;
import com.cly.result.exception.ServiceException;
import com.cly.result.exenum.RequestException;
import com.cly.utils.*;
import io.netty.buffer.ByteBufAllocator;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.*;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 过滤器配置类
 *
 * @author by 春风能解释
 * <p>
 * 2024/6/7
 */
@Configuration
@Slf4j
public class FilterConfiguration {

    /**
     * redis客户端工具类
     */
    @Resource
    private RedisClientsUtils redisClientsUtils;

    /**
     * 鉴权+动态权限过滤器
     *
     * @return 自定义过滤器
     */
    @Order(NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1)
    @Bean
    public GlobalFilter globalFilter() {
        return (exchange, chain) -> {
            // 获取请求信息
            ServerHttpRequest request = exchange.getRequest();
            // 获取请求路径
            String url = request.getURI().getPath();
            // 判断是否排除
            if (ExcludeUrlUtils.isExcludeUrl(url) != 0) {
                // 直接放行
                return chain.filter(exchange);
            }
            // 获取请求头
            HttpHeaders headers = request.getHeaders();
            // 获取token
            String token = headers.getFirst(UserConstants.AUTHORIZATION);
            // 判断token是否为空
            if (!StringUtils.hasText(token)) {
                // 抛出异常
                throw new ServiceException(RequestException.FORBIDDEN);
            }
            // 获取用户id
            Long userId = TokenUtils.checkToken(token, Long.class);
            // 判断token是否一致
            String cacheToken = redisClientsUtils.get(CacheKeysConstants.UserKeys.USER_TOKEN + userId);
            // token不一致
            if (!token.equals(cacheToken)) {
                throw new ServiceException(RequestException.FORBIDDEN);
            }
            // 续期
            redisClientsUtils.reTtl(CacheKeysConstants.UserKeys.USER_TOKEN + userId, CacheKeysConstants.CacheTimes.TOKEN_TIME);
            // 获取请求方法
            String method = request.getMethod().name();
            // 获取redis中保存的路由列表
            String value = redisClientsUtils.get(CacheKeysConstants.UserKeys.USER_ROUTER_ARRAY + token);
            List<Router> routers = List.of();
            // 判断路由列表是否为空
            if (!value.isEmpty()) {
                // 反序列化路由列表
                routers = JsonSerializeUtils
                        .reSerializeList(value, Router.class);
                // 续期
                redisClientsUtils.reTtl(CacheKeysConstants.UserKeys.USER_ROUTER_ARRAY + token, CacheKeysConstants.CacheTimes.PERMISSION_LIST);
            }
            // 判断请求是否在路由列表中
            if (!ExcludeUrlUtils.isMatch(new Router()
                    .setMethod(method)
                    .setUri(url), routers)) {
                // 抛出异常
                throw new ServiceException(RequestException.UNAUTHORIZED);
            }
            // 更新当前用户ID
            redisClientsUtils.setGlobalData(UserConstants.ID, userId);
            // 放行
            return chain.filter(exchange);
        };
    }

    /**
     * xss过滤器
     * <p>
     * 基于若依的xss过滤器实现
     *
     * @return xss过滤器
     */
    @Order(-100)
    @Bean
    public GlobalFilter xssFilter() {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            //获取请求方式
            HttpMethod method = request.getMethod();
            // 判断是否排除
            if (ExcludeUrlUtils.isXssUrl(request.getURI().getPath())) {
                return chain.filter(exchange);
            }
            // 请求为GET或DELETE请求直接放行
            if (method.equals(HttpMethod.GET) || method.equals(HttpMethod.DELETE)) {
                return chain.filter(exchange);
            }
            //非json请求不进行过滤
            if (!StringUtils.startsWithIgnoreCase(request.getHeaders()
                            .getFirst(HttpHeaders.CONTENT_TYPE),
                    MediaType.APPLICATION_JSON_VALUE)) {
                return chain.filter(exchange);
            }
            //主要处理逻辑
            ServerHttpRequestDecorator serverHttpRequestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
                /**
                 * 请求体处理
                 * @return 请求体
                 */
                @Override
                public Flux<DataBuffer> getBody() {
                    Flux<DataBuffer> body = super.getBody();
                    return body.buffer().map(dataBuffers -> {
                        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                        DataBuffer join = dataBufferFactory.join(dataBuffers);
                        byte[] content = new byte[join.readableByteCount()];
                        join.read(content);
                        DataBufferUtils.release(join);
                        String bodyStr = new String(content, StandardCharsets.UTF_8);
                        log.info("原始请求体：{}", bodyStr);
                        // 防xss攻击过滤
                        bodyStr = EscapeUtil.clean(bodyStr);
                        log.info("过滤后的请求体：{}", bodyStr);
                        // 转成字节
                        byte[] bytes = bodyStr.getBytes(StandardCharsets.UTF_8);
                        NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
                        DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
                        buffer.write(bytes);
                        return buffer;
                    });
                }

                /**
                 * 请求头处理
                 * @return 请求头
                 */
                @Override
                public HttpHeaders getHeaders() {
                    HttpHeaders httpHeaders = new HttpHeaders();
                    httpHeaders.putAll(super.getHeaders());
                    // 由于修改了请求体的body，导致content-length长度不确定，因此需要删除原先的content-length
                    httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
                    httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                    return httpHeaders;
                }
            };
            return chain.filter(exchange.mutate().request(serverHttpRequestDecorator).build());
        };
    }

    /**
     * 自定义消息转换器
     *
     * @param converters 转换器列表
     * @return 自定义消息转换器
     */
    @Bean
    @ConditionalOnMissingBean
    public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
        return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
    }

    /**
     * 自定义异常处理器
     *
     * @return 自定义异常处理器
     */
    @Bean
    @Order(-1)
    public ErrorWebExceptionHandler errorWebExceptionHandler() {
        return (exchange, ex) -> {
            // 获取响应信息
            ServerHttpResponse response = exchange.getResponse();
            if (response.isCommitted()) {
                return Mono.error(ex);
            }
            // 设置响应格式为json
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
            response.setRawStatusCode(
                    ex instanceof ServiceException ?
                            ((ServiceException) ex).getStatus()// 获取自定义的状态码
                            : RequestException.UNKNOWN_EXCEPTION.getStatus());
            // 返回响应
            return response.writeWith(Mono.fromSupplier(() -> {
                DataBufferFactory dataBufferFactory = response.bufferFactory();
                try {
                    return dataBufferFactory.wrap(
                            JsonRequest.error(ex)
                                    .toString().getBytes());
                } catch (Exception e) {
                    throw new ServiceException(e);
                }
            }));
        };
    }

}
